package server;

import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;





import com.mysql.jdbc.Connection;

/**
 * holds the referencepoints and the testpoints and provides the basic functionalities for positioning
 * @author nemi
 *
 */
public class Positioning {
	
	private int samplesToGo=0; //indicates the number of samples that will be added to aktRefPoint when mode=REFPOINTSAMPLER
	public int getSamplesToGo() {
		return samplesToGo;
	}

	public ArrayList<ReferencePoint> myRefPoints=null;
	private ReferencePoint aktRefPoint=null; //aktuell ausgewaehlter Referenzpunkt
	private ArrayList<TestPoint> myTestPoints;
	public ArrayList<ReferencePoint> myFixSpots=null;
	
	final int BADRSSI=-45;//Constants that represent poor values of the metrics in case a spot is not seen
	final int BADLQI=100;
	final int BADRLR=0;
	
	final String positioningName="positioning1"; //name of the positioning. Static at the moment
	
	private SampleMode mode; //one of REFPOINTSAMPLER,TESTPOINTSAMPLER, IDLE
	
	
	public Positioning() {
		super();
		myRefPoints= new ArrayList<ReferencePoint>();
		myTestPoints=new ArrayList<TestPoint>();
		myFixSpots=new ArrayList<ReferencePoint>();
		//generateTestData();
		Database.initDatabase();
		myRefPoints.addAll(Database.readReferenzPointsFromDB(positioningName));
		mode=SampleMode.IDLE;
		if(myRefPoints.isEmpty()==false){
			initRefPoints();
		}
		showMeanValues();
		//generateDemoPoints();
	}
	
	/**
	 * calculates the meanvalues of all ReferencePoints
	 */
	public void initRefPoints(){
		for(ReferencePoint refpoint:myRefPoints){
			for(SinglePoint singlepoint:refpoint.singlePoints){
				singlepoint.samples.addAll(Database.readDataSetsFromDB(singlepoint.tablename));
				singlepoint.calculateMeanValues();
			}			
		}
	}
	
	/**
	 * removes the ReferencePoint at Position (x,y) if it exists
	 * @param x
	 * @param y
	 * @return true if successfull deleted, false if not found
	 */
	public boolean removeRefPoint(int x, int y){
		for(ReferencePoint refPoint:myRefPoints){
			if(refPoint.X==x&&refPoint.Y==y){
				myRefPoints.remove(refPoint);
				Database.writeReferenzPointsToDB(positioningName, myRefPoints);
				return true;
			}
		}
		return false;
	}
	
	/**
	 * removes all ReferencePoints
	 */
	public void removeAllRefPoints(){
		myRefPoints.clear();
		Database.writeReferenzPointsToDB(positioningName, myRefPoints);
	}
	
	/**
	 * stops the positioning and sets the server to IDLE-mode
	 */
	public void stopPositioning(){
		mode=SampleMode.IDLE;
	}
	
	
	/**
	 * depending on the server-mode new samples are added to a referencepoint, used to generate a new testpoint
	 * or just ignored
	 * @param newSamples sample input for the server (from basestation)
	 */
	public void addSamples(ArrayList<DataSet> newSamples){
		if(mode==SampleMode.REFPOINTSAMPLER){
			if(samplesToGo>0){
				System.out.println("adding samples");
				aktRefPoint.addSamples(newSamples);
				samplesToGo--;
				System.out.println("samplesToGo= " + samplesToGo);
			}
		}
		else if(mode==SampleMode.TESTPOINTSAMPLER){
			TestPoint newTestPoint=determinePosition(newSamples);
				for(String spot :newTestPoint.meanvalues.keySet())
				System.out.println("TestPoint ; Spot : " + spot + " RSSIMV : " + newTestPoint.meanvalues.get(spot).getMeanrssi()+ " LQIMV : " + newTestPoint.meanvalues.get(spot).getMeanlqi()+" PacketlossRate : " + newTestPoint.meanvalues.get(spot).getPacketLossRate() + " calculated from "+newTestPoint.meanvalues.get(spot).getCount()+ " samples");
			for(int i=0;i<newTestPoint.nearRSSIRefPoints.size();i++){
				System.out.println(newTestPoint.nearRSSIRefPoints.get(i).aRefPoint.id);
				System.out.println(newTestPoint.nearRSSIRefPoints.get(i).distance);
			}
			myTestPoints.add(newTestPoint);
			Database.writeDataSetsToDB("testpoint"+String.valueOf(myTestPoints.size()), newSamples);
		}
	}
		
	
	/**
	 * generates a new TestPoint and calls algorithm()
	 * @param samples
	 * @return
	 */
	public TestPoint determinePosition(ArrayList<DataSet> samples){
		System.out.println("determineposition()");
		TestPoint pointToTest=new TestPoint(samples);
		
		algorithm(pointToTest,false,false);
	
		return pointToTest;
	}
	

	/**
	 * assigns a sorted list of RefPointdistances to testPoint for all metrics
	 * @param testPoint the Point that is compared to the ReferencePoints
	 * @param usefactor1 
	 * @param usefaktor2
	 * @return the modified testPoint
	 */
	private TestPoint algorithm(TestPoint testPoint, boolean usefactor1,boolean usefaktor2){		
		//System.out.println("algorithm()");
		ArrayList<RefPointDistance> unsortedRssi=new ArrayList<RefPointDistance>();
		ArrayList<RefPointDistance> unsortedLqi=new ArrayList<RefPointDistance>();
		ArrayList<RefPointDistance> unsortedPlr=new ArrayList<RefPointDistance>();
		
		//go through all ReferencePoints
		for(ReferencePoint refPoint:myRefPoints){			
			//go through all SinglePoints of every ReferencePoint
			for(int i=0;i<refPoint.singlePoints.size();i++){
				//calculate the euclidean distance to all referencepoints in every metric
				double rssidistance=euklidDistance(refPoint.singlePoints.get(i), testPoint,"RSSI",usefactor1,usefaktor2);
				unsortedRssi.add(new RefPointDistance(refPoint, rssidistance, i));
				
				double lqidistance=euklidDistance(refPoint.singlePoints.get(i), testPoint,"LQI",usefactor1,usefaktor2);
				unsortedLqi.add(new RefPointDistance(refPoint, lqidistance, i));
				
				double plrdistance=euklidDistance(refPoint.singlePoints.get(i), testPoint,"PLR",usefactor1,usefaktor2);
				unsortedPlr.add(new RefPointDistance(refPoint, plrdistance, i));				
			}
		}
		
		//assign sorted RSSI-RefPointDistance to testPoint
		while(unsortedRssi.isEmpty()==false){
			RefPointDistance min=unsortedRssi.get(0);
			for(int i=0;i<unsortedRssi.size();i++){
				if(unsortedRssi.get(i).distance<min.distance){
					min=unsortedRssi.get(i);
				}
			}
			testPoint.nearRSSIRefPoints.add(min);
			unsortedRssi.remove(min);
		}
		
		
		//assign sorted LQI-RefPointDistance to testPoint
		while(unsortedLqi.isEmpty()==false){
			RefPointDistance min=unsortedLqi.get(0);
			for(int i=0;i<unsortedLqi.size();i++){
				if(unsortedLqi.get(i).distance<min.distance){
					min=unsortedLqi.get(i);
				}
			}
			testPoint.nearLQIRefPoints.add(min);
			unsortedLqi.remove(min);
		}
		
		
		//assign sorted PLR-RefPointDistance to testPoint
		while(unsortedPlr.isEmpty()==false){
			RefPointDistance min=unsortedPlr.get(0);
			for(int i=0;i<unsortedPlr.size();i++){
				if(unsortedPlr.get(i).distance<min.distance){
					min=unsortedPlr.get(i);
				}
			}
			testPoint.nearPLRRefPoints.add(min);
			unsortedPlr.remove(min);
		}
		
		return testPoint;		
	}
	
	
	
	private SinglePoint normalize(SinglePoint pointToNormalize){
		SinglePoint result=new SinglePoint("normierung");
		double sum=0;
		for(String spot:pointToNormalize.meanvalues.keySet()){
			sum=sum+pointToNormalize.meanvalues.get(spot).getMeanrssi();
		}
		
		sum=Math.sqrt(sum);
		
		for(String spot:pointToNormalize.meanvalues.keySet()){
			result.meanvalues.get(spot).setMeanrssi(pointToNormalize.meanvalues.get(spot).getMeanrssi()/sum);
		}
		
		
		return result;
	}
	
	
	private double normEuklidDistance(SinglePoint refPoint,SinglePoint testPoint, String metric){
		
		if(metric.equals("RSSI")){
			
			for(String spot:refPoint.meanvalues.keySet()){
				
				if(!testPoint.meanvalues.containsKey(spot))
				{
					testPoint.meanvalues.put(spot, new MeanValue(0, 1, BADRSSI, BADLQI, 0));
				}	
			}
			
			for(String spot:testPoint.meanvalues.keySet()){
				
				if(!refPoint.meanvalues.containsKey(spot))
				{
					refPoint.meanvalues.put(spot, new MeanValue(0, 1, BADRSSI, BADLQI, 0));
				}	
			}
			
			
			refPoint=normalize(refPoint);
			testPoint=normalize(testPoint);
			
			
			return euklidDistance(refPoint,testPoint, metric,false,false);
			
			
		}
		
		return -1;
		
		
	}
	
	
	private double normalizedEuklidDistance(SinglePoint refPoint,SinglePoint testPoint, String metric){
		double temp=0;
		double temp2;
		
		if(metric.equals("RSSI")){
			refPoint.calculateStandardDeviation();
			
			
			for(String spot:refPoint.meanvalues.keySet()){
				
				if(!testPoint.meanvalues.containsKey(spot))
				{
					testPoint.meanvalues.put(spot, new MeanValue(0, 1, BADRSSI, BADLQI, 0));
				}	
			}
			
			for(String spot:testPoint.meanvalues.keySet()){
				
				if(!refPoint.meanvalues.containsKey(spot))
				{
					refPoint.meanvalues.put(spot, new MeanValue(0, 1, BADRSSI, BADLQI, 0));
				}	
			}
			
						
			for(String spot:refPoint.meanvalues.keySet()){
				temp2=(refPoint.meanvalues.get(spot).getMeanrssi()-(testPoint.meanvalues.get(spot).getMeanrssi()));
				temp=temp+temp2*temp2/refPoint.meanvalues.get(spot).getSdrssi();
			}
			
			return Math.sqrt(temp);
			
			
			
		}		
		
		return 0;
	}
	
	
	
	
	/**
	 * determines the euklidean distance between the refPoint and the testPoint in the given metric	
	 * @param refPoint point 1
	 * @param testPoint point 2
	 * @param metric one of RSSI,LQI or PLR
	 * @param usefactor1 take into account, how often a beacon spot was seen at the reference point
	 * @param usefactor2 take into account, how often a beacon spot was seen at the test point
	 * @return the distance in the given metric
	 */
	private double euklidDistance(SinglePoint refPoint,SinglePoint testPoint, String metric,boolean usefactor1,boolean usefactor2){
		double distance=Double.POSITIVE_INFINITY;
		double disttemp=0;
	
				
		int gesamtf1=0;
		for(MeanValue mv: refPoint.meanvalues.values()){
			gesamtf1+=mv.getCount();
		}
		int gesamtf2=0;
		for(MeanValue mv: testPoint.meanvalues.values()){
			gesamtf2+=mv.getCount();
		}
		
		//run through components of the refPoint
		for(String spot: refPoint.meanvalues.keySet()){
			
			double temp=Double.POSITIVE_INFINITY;
			double f1=1;
			double f2=1;
			if(testPoint.meanvalues.containsKey(spot)){
				if(metric.equals("RSSI")){
					temp=(refPoint.meanvalues.get(spot).getMeanrssi()-(testPoint.meanvalues.get(spot).getMeanrssi()));
				}
				else if(metric.equals("LQI")){
					temp=(refPoint.meanvalues.get(spot).getMeanlqi()-(testPoint.meanvalues.get(spot).getMeanlqi()));
				}
				else if(metric.equals("PLR")){
					temp=(refPoint.meanvalues.get(spot).getPacketLossRate()-(testPoint.meanvalues.get(spot).getPacketLossRate()));
				}					
				if(usefactor2){
					f2=((double) testPoint.meanvalues.get(spot).getCount()/ (double) gesamtf2);
				}
			}
			else{
				if(metric.equals("RSSI")){
					temp=(refPoint.meanvalues.get(spot).getMeanrssi()-(BADRSSI));
				}
				else if(metric.equals("LQI")){
					temp=(refPoint.meanvalues.get(spot).getMeanlqi()-(BADLQI));
				}
				else if(metric.equals("PLR")){
					temp=(refPoint.meanvalues.get(spot).getPacketLossRate()-(BADRLR));
				}					
				if(usefactor2){
					f2=0;
				}
			}
			if(usefactor1){
				f1=((double) refPoint.meanvalues.get(spot).getCount()/ (double) gesamtf1);					
			}
			disttemp+=f1*f2*temp*temp;
		}
		
		//take into account spots that were eventually not seen at the refPoint
		for(String spot: testPoint.meanvalues.keySet()){
			double temp=Double.POSITIVE_INFINITY;
			double f1=1;
			double f2=1;
			if(!refPoint.meanvalues.containsKey(spot)){
				if(metric.equals("RSSI")){
					temp=(testPoint.meanvalues.get(spot).getMeanrssi()-(BADRSSI));
				}
				else if(metric.equals("LQI")){
					temp=(testPoint.meanvalues.get(spot).getMeanlqi()-(BADLQI));
				}
				else if(metric.equals("PLR")){
					temp=(testPoint.meanvalues.get(spot).getPacketLossRate()-(BADRLR));
				}		
				
				if(usefactor1){
					f1=0;				
				}
				
				if(usefactor2){
					f2=((double) testPoint.meanvalues.get(spot).getCount()/ (double) gesamtf2);
				}
				disttemp+=f1*f2*temp*temp;
			}
			
		}
		distance=Math.sqrt(disttemp);
		return distance;		
	}
	
	public void testEuklid(){
		DataSet[] refPoint=new DataSet[4];
		DataSet[] testPoint=new DataSet[4];
		
		for(int i=0;i<4;i++){
			refPoint[i]=new DataSet();
			testPoint[i]=new DataSet();
		}
		
		refPoint[0].setLQI(1);
		refPoint[1].setLQI(2);
		refPoint[2].setLQI(3);
		refPoint[3].setLQI(4);
		
		testPoint[0].setLQI(4);
		testPoint[1].setLQI(3);
		testPoint[2].setLQI(2);
		testPoint[3].setLQI(1);
		
		//double distance= euklidDistance(refPoint, testPoint, "LQI");
		
		//System.out.println(distance);
		
		
	}
	
	public void testDeterminePos(){
	ArrayList<DataSet> testPointSet=new ArrayList<DataSet>();				
		for(int i=0;i<100;i++){
			DataSet testSet=new DataSet();
			testSet.setLQI(i);
			testSet.setRssi((int)Math.round(Math.random()*-45));
			System.out.println(testSet.getRssi());
			testSet.setSendingTime(String.valueOf(i));
			testSet.setSeqNr(i);
			testSet.setSpotId("testSpot1");
			testSet.setTime(String.valueOf(i));
			testPointSet.add(testSet);
		}
		for(int i=0;i<100;i++){
			DataSet testSet=new DataSet();
			testSet.setLQI(i);
			testSet.setRssi((int)Math.round(Math.random()*-45));
			testSet.setSendingTime(String.valueOf(i));
			testSet.setSeqNr(i);
			testSet.setSpotId("testSpot2");
			testSet.setTime(String.valueOf(i));
			testPointSet.add(testSet);
		}
		for(int i=0;i<100;i++){
			DataSet testSet=new DataSet();
			testSet.setLQI(i);
			testSet.setRssi((int)Math.round(Math.random()*-45));
			testSet.setSendingTime(String.valueOf(i));
			testSet.setSeqNr(i);
			testSet.setSpotId("testSpot3");
			testSet.setTime(String.valueOf(i));
			testPointSet.add(testSet);
		}
		
		System.out.println("teste determinePosition...");
		TestPoint result=determinePosition(testPointSet);
		//System.out.println("ergebnisresult: "+result.getNearestX("RSSI").aRefPoint.id);
		for(RefPointDistance ref:result.nearRSSIRefPoints){
			System.out.println("zu Punkt "+ref.aRefPoint.id + "RSSI distance " + ref.distance);
		}
		
		
		
			
				
			

	}
	public void generateTestData(){
		
		for(int k=0;k<10;k++){
			this.addRefPoint(k, k, 7);
			ArrayList<DataSet> referencePointSet=new ArrayList<DataSet>();			
			for(int i=0;i<100;i++){
				DataSet testSet=new DataSet();
				testSet.setLQI(i);
				testSet.setRssi(((int)Math.round(Math.random()*-45)));
				testSet.setSendingTime(String.valueOf(i));
				testSet.setSeqNr(i);
				testSet.setSpotId("testSpot1");
				testSet.setTime(String.valueOf(i));
				referencePointSet.add(testSet);
			}
			for(int i=0;i<100;i++){
				DataSet testSet=new DataSet();
				testSet.setLQI(i*(k+1));
				testSet.setRssi(((int)Math.round(Math.random()*-45)));
				testSet.setSendingTime(String.valueOf(i));
				testSet.setSeqNr(i);
				testSet.setSpotId("testSpot2");
				testSet.setTime(String.valueOf(i));
				referencePointSet.add(testSet);
			}
			for(int i=0;i<100;i++){
				DataSet testSet=new DataSet();
				testSet.setLQI(i);
				testSet.setRssi(((int)Math.round(Math.random()*-45)));
				testSet.setSendingTime(String.valueOf(i));
				testSet.setSeqNr(i);
				testSet.setSpotId("testSpot3");
				testSet.setTime(String.valueOf(i));
				referencePointSet.add(testSet);
			}
			
			
				this.addSamples(referencePointSet);
			
		}			
	}
	
	public void showMeanValues(){
		System.out.println("show RefPoints: ");
		for(ReferencePoint refPoint:myRefPoints){
			for(SinglePoint singlePoint:refPoint.singlePoints){
				for(String spot :singlePoint.meanvalues.keySet())
				System.out.println("Pos " + refPoint.id + " ; Spot : " + spot + " RSSIMV : " + singlePoint.meanvalues.get(spot).getMeanrssi()+ " LQIMV : " + singlePoint.meanvalues.get(spot).getMeanlqi()+" PacketlossRate : " + singlePoint.meanvalues.get(spot).getPacketLossRate());
			}
		}
	}

	public void addRefPoint(int x, int y, int sampleCount) {
		aktRefPoint=new ReferencePoint(x,y,String.valueOf(x)+"_"+String.valueOf(y));
		myRefPoints.add(aktRefPoint);
		aktRefPoint.addSinglePoint(this);
		//samplesToGo=sampleCount;
		//mode=SampleMode.REFPOINTSAMPLER;
	}

	public void addSampleToRefPoint(int x, int y, int sampleCount,
			boolean keepTable) {
		System.out.println("addsamplestorefpoint");
		boolean found=false;
		for(int i=0;i<myRefPoints.size();i++){ //suchen, ob es den Punkt schon gibt
			if(myRefPoints.get(i).X==x && myRefPoints.get(i).Y==y){
				aktRefPoint=myRefPoints.get(i);
				found=true;
			}
		}
		if(found){
			if(!keepTable){
				aktRefPoint.addSinglePoint(this);			
			}
			samplesToGo=sampleCount;
			mode=SampleMode.REFPOINTSAMPLER;
		}
	
		
	}
	
	public ReferencePoint getLastSpotPosition(){
		mode=SampleMode.TESTPOINTSAMPLER;
		if(myTestPoints.isEmpty()){
			ReferencePoint dummy=new ReferencePoint(0, 0, "leer");
			return dummy;
		}
		else{
			TestPoint lastone=myTestPoints.get(myTestPoints.size()-1);
			return lastone.getNearestX("RSSI").aRefPoint;
		}
		
	}
	
	public String getSpotToLocate(){
		if(myTestPoints.isEmpty()){
			return "0000.0000.0000.0000";
		}
		else{
			TestPoint lastone=myTestPoints.get(myTestPoints.size()-1);
			return lastone.ownSpotID;
		}
		
		
	}
	
	private void generateDemoPoints(){
		for(int i=0;i<7;i++){
			for(int k=0;k<8;k++){
				this.addRefPoint(55+i*107, 50+k*75, 10);
			}
		}
	}
	
	
	}
